package com.ys.spring.mvc;

import com.ys.spring.biz.UserController;
import com.ys.spring.mvc.annotation.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class MyDispatcherServlet extends HttpServlet {

    private static final String CONFIG_NAME = "applicationContext.properties";

    private static final String SCAN_PACKAGE_KEY = "scan.package";

    private static Properties properties;

    private static Map<String, Class> classMap = new HashMap<String, Class>();

    private static Map<String, Object> beanMap = new HashMap<String, Object>();

    private static String classPath;

    private static Map<String, Method> handlerMapping = new HashMap<String, Method>();

    @Override
    public void init() throws ServletException {
        super.init();
        loadConfig();
        scanClass();
        createBean();
        dependencyInsert();
        handlerMapping();
    }

    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        String relativeUrl = requestURI.replaceAll(contextPath, "").replaceAll("/+", "/");
        String[] split = relativeUrl.split("/");
        String controllerUrl = split[1];
        String methodUrl = split[2];
        Object controller = beanMap.get(controllerUrl);
        Class[] clazzs = new Class[]{};
        try {
            Method declaredMethod = controller.getClass().getDeclaredMethod(methodUrl, clazzs);
            declaredMethod.setAccessible(true);
            Object invoke = declaredMethod.invoke(controller);
            resp.getWriter().write((String) invoke);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void handlerMapping() {
//       for(Map.Entry entry : classMap.entrySet()){
//           Class value = (Class) entry.getValue();
//           Method[] methods = value.getDeclaredMethods();
//           for(Method method : methods) {
//               Annotation[] annotations = method.getAnnotations();
//               for(Annotation annotation : annotations) {
//                   if(Arrays.asList(RequestMapping.class, GetMapping.class).contains(annotation.annotationType().getClass())){
//                       Annotation controllerAnnotation = value.getAnnotation(RequestMapping.class);
//                       controllerAnnotation.
//                       handlerMapping.put(annotation, method);
//                   }
//               }
//           }
//       }
    }

    private void dependencyInsert() {
        for(Map.Entry entry : classMap.entrySet()){
            Object key = entry.getKey();
            Class value = (Class) entry.getValue();
            Field[] fields = value.getDeclaredFields();
            for (Field field : fields) {
                Annotation[] annotations = field.getAnnotations();
                for(Annotation annotation : annotations){
                    if(annotation.annotationType() == Autowire.class) {
//                        String name = field.getName();
//                        Object o = beanMap.get(getObjectName(value.getName()));
                        field.setAccessible(true);
                        try {
                            field.set(beanMap.get(getObjectName(value.getName())), beanMap.get(field.getName()));
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    private void createBean() {
        log("start createBean");
        for(Map.Entry entry : classMap.entrySet()) {
            String key = (String) entry.getKey();
            Class value = (Class) entry.getValue();
            if(value.getAnnotation(Controller.class) != null || value.getAnnotation(Service.class) != null){
                try {
                    Object instance = value.newInstance();
                    beanMap.put(getObjectName(value.getName()), instance);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            log("end createBean");
        }
    }

    private String getObjectName(String name) {
        String[] split = name.split("\\.");
        char[] chars = split[split.length - 1].toCharArray();
        chars[0] += 32;
        return String.copyValueOf(chars);
    }

    private void scanClass() {
        log("start scanClass");
        String property = properties.getProperty(SCAN_PACKAGE_KEY);
        String path = this.getClass().getClassLoader().getResource(CONFIG_NAME).getPath();
        String scanClassPath = path.replace(CONFIG_NAME, "") + File.separator + property.replace(".", File.separator);
        File file = new File(scanClassPath);
        getClass2Container(file);
        log("end scanClass");
    }

    private void getClass2Container(File file) {
        log("start getClass2Container");
        if(file.isDirectory()){
            File[] files = file.listFiles();
            for (File aFile : files){
                getClass2Container(aFile);
            }
        }
        if(file.getPath().contains(".class")){
            try {
                Class<?> aClass = Class.forName(properties.get(SCAN_PACKAGE_KEY) + ".biz." +file.getName().replace(".class", ""));
                classMap.put(file.getName(), aClass);
            } catch (ClassNotFoundException e) {
               System.out.println("Can not find the class " + file.getPath());
            }
        }
        log("end getClass2Container");
    }

    public void log(String log){
        System.out.println(log);
    }
    private void loadConfig() {
        log("start loadConfig");
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(CONFIG_NAME);
        if(properties == null){
            properties = new Properties();
            try {
                properties.load(is);
            } catch (IOException e) {
                throw new RuntimeException("Load applicationContext.properties Fail.");
            }
        }
        log("end loadConfig");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        log("start doGet");
        doPost(req, resp);
        log("end doGet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        log("start doPost");
        try {
            doDispatcher(req, resp);
        } catch (Exception e){
            throw new RuntimeException("500 Server Exception");
        }
        log("end doPost");
    }

    @Override
    public void destroy() {
        super.destroy();
    }
}
